無論是使用者輸入老師或是課名,同樣都是使用 PostbackEvent 觸發事件搭配 FlexSendMessage 訊息回覆,就能跳出課程列表或是開課老師列表,其邏輯也大致相同。
如果要完全再寫一個一樣的函式處理課名,也是完全可行的,但若我們使用了同一份 teacher_lists_flex_message_package 呢?
無論課程列表或是開課老師列表都重複利用同一個函式,這樣程式碼就會變得更簡潔,但如果太簡潔又會看不懂,所以兼具易讀與重複利用高的平衡,是所有軟體工程師的生涯課題,畢竟通常專案都會有一群人一起維護,不像是自己的房間要多亂都沒關西。
更名檔案使其與涵義一致:因為都是使用同一套模板,先將 reply_course_list.json 重新取名,改為 reply_course_teacher_list.json,養成好習慣,以便日後辨識。
條件句判斷查詢老師/課名:在監聽使用者傳來的文字訊息時,也要判斷傳來的是課名還是老師名,順手也將函式改為 handle_msg:
# hulolo > chatbot > views.py
@parser.add(MessageEvent, message=TextMessage)
def handle_msg(event):    
    user_message = event.message.text  #取得使用者發送的文字
    # 以不同條件做搜尋
    filtered_teacher = Course.objects.filter(teacher_name=user_message)
    filtered_course = Course.objects.filter(course_name=user_message)
透過二分法設計條件句,先添上老師名的判斷:
# hulolo > chatbot > views.py
# 接續上段程式碼
    # 判斷老師名的物件是否存在資料
    if filtered_teacher.exists():
            teacher_name = user_message
            candidate_courses = filtered_teacher.values_list(
                'course_name', flat=True).distinct()
            # 修改函式名稱為 dynamic_flex_message_package
            # 多了 label_type 參數讓函式能辨識來源為何
            flex_message = dynamic_flex_message_package(
                message = FlexSendMessage(
                alt_text=f"{teacher_name} 老師的課程",
                contents=flex_message
                )
            line_bot_api.reply_message(
                event.reply_token,
                message
                )
elif 的部分再添上課程名的判斷:
# hulolo > chatbot > views.py
# 接續上段程式碼
    # 判斷課程名的物件是否存在資料
    elif filtered_course.exists():
        course_name = user_message
        candidate_teachers = filtered_course.values_list(
        'teacher_name', flat=True).distinct()
        # 同樣地,稍後會製作這個漂亮的函式,先呼叫它
        # 多了 label_type 變數讓函式能辨識
        flex_message = dynamic_flex_message_package(
        course_name, candidate_teachers, label_type='teacher')
        message = FlexSendMessage(
                            alt_text=f"{course_name} 的老師",
                            contents=flex_message
                        )
        line_bot_api.reply_message(
                    event.reply_token,
                    message)
Flex message 的模板處理: dynamic_flex_message_package引入了三個參數,也做了更名,改為較為中性的 title_name 以及 candidate_list,也別忘了 label_type:
# hulolo > chatbot > views.py
def dynamic_flex_message_package(title_name, candidate_list, label_type):
    # 引入 JSON 檔案
    json_path = os.path.join(BASE_DIR, 'chatbot', 'reply_course_teacher_list.json')
    flex = json.load(open(json_path, 'r', encoding='utf-8'))    
    # 設定標題為傳入的名稱 (可以是老師名或課程名) 這邊使用了單行條件句
    flex['body']['contents'][0]['text'] = f"哪位老師的{title_name}?" if 
        label_type == 'teacher' else f"{title_name}老師的哪堂課?"
    # 設定顏色列表
    colors = ['#F0C29E', '#A1DE95', '#F5C578', '#91D9C2', '#DFC493', 
              '#F0C29E', '#A1DE95', '#F5C578', '#91D9C2', '#DFC493']
根據傳入的 candidate_list 動態生成按鈕:
    # hulolo > chatbot > views.py
    # 接續上方程式碼
    # 根據傳入的 candidate_list 動態生成按鈕
    for i, name in enumerate(candidate_list, start=1):
    button = {
        'type': 'box',
        'layout': 'vertical',
        'spacing': 'none',
        'contents': [{
            'type': 'button',
            'style': 'primary',
            'action': {
                'type': 'postback',
                'label': name,
                # 注意這裡的 data 格式,使用了二分法,根據傳入的參數動態生成資料
                # 因為兩者課名與老師名的順序對調會讓 handle_postback 失效 
                'data': f"{name}-{title_name}" if 
                    label_type == "teacher" else f"{title_name}-{name}" 
            },
            'color': colors[i-1],
            'margin': 'xs',
            'offsetTop': 'none',
            'height': 'sm'
        }],
        'flex': 0,
        'borderWidth': 'medium',
        'cornerRadius': 'xxl',
        'offsetTop': 'none',
        'margin': 'md',
        'backgroundColor': colors[i-1]
        }
        flex['body']['contents'].append(button)
    return flex
編寫的時候別把課名、老師名相互搞混了~
無論輸入 吳小峰 或 特殊教育學生評量 都能夠跑出對應的列表囉:
handle_msg 函式中的條件句其實也有高度類似,可以試著重構讓其更簡潔,但別顧著簡潔失了可讀性。在這篇文章中,我們學會了:
label_type 參數的代入能夠判斷主程式的類型 (以老師名查詢/以課名查詢)title_name 及 candidate_list 中性的命名可以容易了解變數的用途
teacher_name 及 candidate_courses